The fish belt transect (BLT) protocol is the primary method used to quantify reef fish assemblages at UVS sites. Divers swim standardized belt transects, visually identifying and sizing all fishes observed within a defined area. This method yields robust estimates of fish abundance, biomass, and community structure across depth strata and regions (see SOP for full methods).
Fish BLT data are organized across three interlinked tables:
uvs.blt_stations - Metadata for each station, including depth, diver, transect area, and summary metrics
uvs.blt_observations - Raw fish counts by species, size class, and transect replicate
uvs.blt_biomass_by_taxa - Derived biomass by station and taxon, calculated using standard length–weight relationships
All records are linked by ps_station_id, and species are standardized via accepted_aphia_id (foreign key to taxonomy.fish).
Stations (uvs.blt_stations)
This table contains one record per depth-stratified fish survey conducted at a UVS site. Each row includes core spatial and survey metadata — such as depth, date, divers, and number of transects — along with ecological summaries (e.g., species richness, fish abundance, biomass).
Stations are linked to uvs.sites via ps_site_id, and uniquely identified using a depth-binned ps_station_id. This table provides the context and summary metrics for interpreting observation-level data.
Standard UVS depth strata
To ensure consistency across regions and survey protocols, all UVS stations are binned into standardized depth strata based on fixed thresholds. These depth bins are appended to the ps_site_id to generate the full ps_station_id.
Depth strata and suffixes:
Supershallow (<= 6 m) → 05m
Shallow (7–14 m) → 10m
Deep (>= 15 m) → 20m
Examples:
A survey at 5.8 m → FJI_2025_uvs_001_05m
A survey at 14.7 m → FJI_2025_uvs_001_10m
This scheme ensures consistent stratification across all analyses, even with natural variation in survey depth.
Table 1: Fields in the uvs.blt_stations table
Field
Type
Required
Description
ps_station_id
STRING
true
Unique station ID (ps_site_id_depth), e.g., CHL_2024_uvs_001_20m.
ps_site_id
STRING
true
◂ Foreign key to uvs.sites table.
exp_id
STRING
true
◂ Expedition ID (ISO3_YEAR).
method
STRING
true
◂ Survey method (uvs).
protocol
STRING
true
Survey protocol (fish_blt).
divers
STRING
true
Comma-separated names of divers who conducted the transects.
region
STRING
true
◂ Region name.
subregion
STRING
true
◂ Subregion name.
locality
STRING
false
◂ Optional local feature (e.g., reef, bay, cove).
latitude
FLOAT
true
◂ Latitude of site (decimal degrees, WGS84).
longitude
FLOAT
true
◂ Longitude of site (decimal degrees, WGS84).
date
DATE
true
◂ Survey date (YYYY-MM-DD).
time
TIME
true
◂ Start time of survey (24-hour format).
depth_m
FLOAT
true
Average depth of the station (m).
depth_strata
STRING
true
Depth bin: supershallow, shallow, or deep.
habitat
STRING
true
◂ Habitat type (see controlled vocabulary).
exposure
STRING
true
◂ Exposure type (see controlled vocabulary).
n_transects
INTEGER
true
Number of belt transects completed at the station.
survey_area_m2
FLOAT
true
Total area surveyed across all transects (m²).
total_richness
INTEGER
false
Total number of unique fish taxa recorded.
total_count
INTEGER
false
Total number of individuals recorded.
avg_richness
FLOAT
false
Mean taxa richness per transect.
avg_count
FLOAT
false
Mean number of individuals per transect.
avg_count_m2
FLOAT
false
Mean fish density (individuals/m²).
avg_biomass_gm2
FLOAT
false
Mean biomass (g/m²).
pct_biomass_top_pred
FLOAT
false
Average % of total biomass contributed by top predators.
pct_biomass_sharks
FLOAT
false
Average % of total biomass contributed by sharks.
notes
STRING
false
Optional comments or QA notes.
Observations (uvs.blt_observations)
This table stores raw fish observations recorded during individual belt transects. Each row captures a unique combination of species, estimated total length (cm), and count, representing a single diver observation within a defined transect area.
In addition to raw counts, it includes derived metrics such as biomass (biomass_g) and biomass density (biomass_gm2), calculated using species-specific length–weight parameters from taxonomy.fish. Observations are linked to their parent station via ps_station_id, and taxonomic identity is standardized using accepted_aphia_id.
Standard transect dimensions
Fish ≥ 20 cm are recorded along a 25 m × 4 m belt (100 m²)
Fish < 20 cm are recorded along a 25 m × 2 m belt (50 m²)
Table 2: fish.observations Table Schema
Field
Type
Required
Description
obs_id
STRING
true
Unique observation ID (e.g., CHL_2024_blt_AMF_0001).
ps_station_id
STRING
true
Foreign key linking to uvs.blt_stations.
diver
STRING
true
Name of the diver who recorded the observation.
transect
STRING
true
Transect label: A, B, or C; OFF for observations outside standard transects.
Recorded depth (m) at which the transect was conducted.
accepted_name
STRING
true
Scientific name (Genus species) of the observed taxon.
accepted_aphia_id
INTEGER
true
WoRMS AphiaID — foreign key to taxonomy.fish.
family
STRING
true
Family of the observed taxon (from taxonomy.fish).
trophic_group
STRING
true
Trophic group classification (e.g., herbivore/detritivore, top-predator).
terminal_phase
BOOLEAN
false
TRUE if the individual was identified as terminal phase (e.g., parrotfishes).
length_cm
FLOAT
true
Estimated total length (cm) of the individual.
count
INTEGER
true
Number of individuals observed in this size class.
count_m2
FLOAT
false
Fish density (individuals/m²), based on size-specific belt area.
biomass_g
FLOAT
false
Total biomass (g), calculated using length–weight relationship.
biomass_gm2
FLOAT
false
Biomass per square meter (g/m²), standardized by belt area.
notes
STRING
false
Optional comments or QA annotations.
Off-transect observations
Fish observed outside of standard transects (e.g., large pelagics, megafauna) are recorded using transect = "OFF". These are included for completeness but should be treated separately in standardized analyses.
Biomass by taxa and station (uvs.blt_biomass_by_taxa)
This table contains station-level summaries of reef fish abundance and biomass by species, derived from raw BLT transect observations. It serves as the primary analysis-ready table for assessing community structure, trophic composition, and biomass patterns.
Each row represents a unique combination of ps_station_id and species. Metrics are expressed as mean values across all transects at the station, with zeros imputed for transects where the species was not observed. This approach ensures unbiased station-level estimates of:
count_m2 - average density (individuals/m²)
biomass_gm2 - average biomass (g/m²)
Species identity is standardized via accepted_aphia_id, with taxonomic and ecological traits (e.g., family, trophic group) joined from taxonomy.fish. These fields support flexible aggregation by trophic or functional guild.
Table 3: Schema for station-level summaries of reef fish biomass and abundance by species
Field
Type
Required
Description
ps_station_id
STRING
true
Unique station ID (ps_site_id_depth), e.g., CHL_2024_uvs_001_20m.
accepted_name
STRING
true
Scientific name of the taxon (Genus species).
accepted_aphia_id
INTEGER
true
Unique AphiaID from WoRMS; foreign key to taxonomy.fish.
family
STRING
true
Family of the taxon (joined from taxonomy.fish).
trophic_group
STRING
true
Trophic group classification (e.g., planktivore, top-predator).
depth_m
FLOAT
true
Mean depth (m) of the station.
depth_strata
STRING
true
Depth bin label: supershallow, shallow, or deep.
region
STRING
true
Region name.
subregion
STRING
true
Subregion (e.g., island, gulf, reef complex).
locality
STRING
false
Optional local feature (e.g., reef, bay, cove).
habitat
STRING
true
Dominant habitat type at the station.
exposure
STRING
true
Wave/wind exposure at the station (windward, leeward, lagoon).
total_count
INTEGER
false
Total number of individuals observed across all transects.
avg_count
FLOAT
false
Mean number of individuals per transect (including zeros).
avg_count_m2
FLOAT
false
Mean density (individuals/m²), with zeros imputed for missing transects.
avg_biomass_gm2
FLOAT
false
Mean biomass (g/m²), with zeros imputed for missing transects.
avg_length_cm
FLOAT
false
Weighted mean total length (cm); weights based on individual counts.
min_length_cm
FLOAT
false
Minimum total length (cm) observed at the station.
max_length_cm
FLOAT
false
Maximum total length (cm) observed at the station.
---title: "Fish Belt Transects (BLT)"format: html: theme: lux self-contained: true code-fold: true toc: true toc-depth: 3 toc-location: right number-sections: true number-depth: 3---```{r setup, message = F, warning = F, fig.width = 10, fig.height = 10, echo = F}options(scipen = 999)library(PristineSeasR)library(bigrquery)library(gt)library(gtExtras)library(tibble)library(tidyverse)library(dplyr)library(ggplot2)library(plotly)library(lubridate)knitr::opts_chunk$set(eval = F, warning = F, message = F, include = F, echo = F)ps_paths <- PristineSeasR::get_sci_drive_paths()prj_path <- file.path(ps_paths$projects, "legacy-db")ps_data_path <- ps_paths$datasetsbigrquery::bq_auth(email = "marine.data.science@ngs.org")project_id <- "pristine-seas"bq_connection <- DBI::dbConnect(bigrquery::bigquery(), project = project_id)```### OverviewThe fish belt transect (BLT) protocol is the primary method used to quantify reef fish assemblages at UVS sites. Divers swim standardized belt transects, visually identifying and sizing all fishes observed within a defined area. This method yields robust estimates of fish abundance, biomass, and community structure across depth strata and regions (see SOP for full methods).Fish BLT data are organized across three interlinked tables:- `uvs.blt_stations` - Metadata for each station, including depth, diver, transect area, and summary metrics- `uvs.blt_observations` - Raw fish counts by species, size class, and transect replicate- `uvs.blt_biomass_by_taxa` - Derived biomass by station and taxon, calculated using standard length–weight relationshipsAll records are linked by `ps_station_id`, and species are standardized via `accepted_aphia_id` (foreign key to `taxonomy.fish`).------------------------------------------------------------------------##### Stations (`uvs.blt_stations`)This table contains one record per depth-stratified fish survey conducted at a UVS site. Each row includes core spatial and survey metadata — such as depth, date, divers, and number of transects — along with ecological summaries (e.g., species richness, fish abundance, biomass).Stations are linked to `uvs.sites` via `ps_site_id`, and uniquely identified using a depth-binned `ps_station_id`. This table provides the context and summary metrics for interpreting observation-level data.::: {.callout-note title="Standard UVS depth strata"}To ensure consistency across regions and survey protocols, all UVS stations are binned into standardized depth strata based on fixed thresholds. These depth bins are appended to the `ps_site_id` to generate the full `ps_station_id`.**Depth strata and suffixes:**- **Supershallow (`<= 6 m`)** → `05m`- **Shallow (`7–14 m`)** → `10m`- **Deep (`>= 15 m`)** → `20m`**Examples:**- A survey at 5.8 m → `FJI_2025_uvs_001_05m`- A survey at 14.7 m → `FJI_2025_uvs_001_10m`This scheme ensures consistent stratification across all analyses, even with natural variation in survey depth.:::```{r eval = T, include = T}#| label: tbl-uvs.blt_stations#| tbl-cap: "Fields in the `uvs.blt_stations` table"blt_stations_fields <- tribble( ~field, ~type, ~required, ~description, # Identifiers "ps_station_id", "STRING", TRUE, "Unique station ID (`ps_site_id_depth`), e.g., `CHL_2024_uvs_001_20m`.", "ps_site_id", "STRING", TRUE, "◂ Foreign key to `uvs.sites` table.", "exp_id", "STRING", TRUE, "◂ Expedition ID (`ISO3_YEAR`).", "method", "STRING", TRUE, "◂ Survey method (`uvs`).", "protocol", "STRING", TRUE, "Survey protocol (`fish_blt`).", "divers", "STRING", TRUE, "Comma-separated names of divers who conducted the transects.", # Spatial and temporal "region", "STRING", TRUE, "◂ Region name.", "subregion", "STRING", TRUE, "◂ Subregion name.", "locality", "STRING", FALSE, "◂ Optional local feature (e.g., reef, bay, cove).", "latitude", "FLOAT", TRUE, "◂ Latitude of site (decimal degrees, WGS84).", "longitude", "FLOAT", TRUE, "◂ Longitude of site (decimal degrees, WGS84).", "date", "DATE", TRUE, "◂ Survey date (`YYYY-MM-DD`).", "time", "TIME", TRUE, "◂ Start time of survey (24-hour format).", # Environmental context "depth_m", "FLOAT", TRUE, "Average depth of the station (m).", "depth_strata", "STRING", TRUE, "Depth bin: `supershallow`, `shallow`, or `deep`.", "habitat", "STRING", TRUE, "◂ Habitat type (see controlled vocabulary).", "exposure", "STRING", TRUE, "◂ Exposure type (see controlled vocabulary).", # Survey effort "n_transects", "INTEGER", TRUE, "Number of belt transects completed at the station.", "survey_area_m2", "FLOAT", TRUE, "Total area surveyed across all transects (m²).", # Summary metrics (transect-level averages) "total_richness", "INTEGER", FALSE, "Total number of unique fish taxa recorded.", "total_count", "INTEGER", FALSE, "Total number of individuals recorded.", "avg_richness", "FLOAT", FALSE, "Mean taxa richness per transect.", "avg_count", "FLOAT", FALSE, "Mean number of individuals per transect.", "avg_count_m2", "FLOAT", FALSE, "Mean fish density (individuals/m²).", "avg_biomass_gm2", "FLOAT", FALSE, "Mean biomass (g/m²).", "pct_biomass_top_pred", "FLOAT", FALSE, "Average % of total biomass contributed by top predators.", "pct_biomass_sharks", "FLOAT", FALSE, "Average % of total biomass contributed by sharks.", # Notes "notes", "STRING", FALSE, "Optional comments or QA notes.")gt(blt_stations_fields) |> cols_label(field = md("**Field**"), type = md("**Type**"), required = md("**Required**"), description = md("**Description**")) |> cols_width(field ~ px(200), type ~ px(100), required ~ px(80), description ~ px(500)) |> data_color(columns = c(field), fn = scales::col_factor(palette = c("#f6f6f6"), domain = NULL) ) |> tab_options(table.font.size = px(13), table.width = pct(100)) |> fmt_tf(columns = required, tf_style = "true-false") |> fmt_markdown(columns = description) |> gt_theme_nytimes()``````{r}# Define table schemablt_stations_schema <- blt_stations_fields |>mutate(mode =if_else(required, "REQUIRED", "NULLABLE")) |>transmute(name = field,type = type,mode = mode,description = description) |>pmap(function(name, type, mode, description) {list(name = name, type = type, mode = mode, description = description) })bq_table_create(bq_table(project_id, "uvs", "blt_stations"),fields = blt_stations_schema)```##### Observations (`uvs.blt_observations`)This table stores raw fish observations recorded during individual belt transects. Each row captures a unique combination of species, estimated total length (cm), and count, representing a single diver observation within a defined transect area.In addition to raw counts, it includes derived metrics such as biomass (`biomass_g`) and biomass density (`biomass_gm2`), calculated using species-specific length–weight parameters from `taxonomy.fish`. Observations are linked to their parent station via `ps_station_id`, and taxonomic identity is standardized using `accepted_aphia_id`.::: {.callout-note title="Standard transect dimensions"}- Fish **≥ 20 cm** are recorded along a 25 m × 4 m belt (100 m²)- Fish **< 20 cm** are recorded along a 25 m × 2 m belt (50 m²):::```{r eval = T, include = T}#| label: tbl-fish-obs-schema#| tbl-cap: "`fish.observations` Table Schema"blt_obs_fields <- tribble( ~field, ~type, ~required, ~description, # Identification and traceability "obs_id", "STRING", TRUE, "Unique observation ID (e.g., `CHL_2024_blt_AMF_0001`).", "ps_station_id", "STRING", TRUE, "Foreign key linking to `uvs.blt_stations`.", "diver", "STRING", TRUE, "Name of the diver who recorded the observation.", "transect", "STRING", TRUE, "Transect label: `A`, `B`, or `C`; `OFF` for observations outside standard transects.", # Spatial context "station_label", "STRING", TRUE, "Field-assigned relative stratum label (e.g., `shallow`, `deep`).", "depth_m", "FLOAT", TRUE, "Recorded depth (m) at which the transect was conducted.", # Taxonomic identity "accepted_name", "STRING", TRUE, "Scientific name (`Genus species`) of the observed taxon.", "accepted_aphia_id", "INTEGER", TRUE, "WoRMS AphiaID — foreign key to `taxonomy.fish`.", "family", "STRING", TRUE, "Family of the observed taxon (from `taxonomy.fish`).", "trophic_group", "STRING", TRUE, "Trophic group classification (e.g., `herbivore/detritivore`, `top-predator`).", # Observation details "terminal_phase", "BOOLEAN", FALSE, "TRUE if the individual was identified as terminal phase (e.g., parrotfishes).", "length_cm", "FLOAT", TRUE, "Estimated total length (cm) of the individual.", "count", "INTEGER", TRUE, "Number of individuals observed in this size class.", # Derived metrics "count_m2", "FLOAT", FALSE, "Fish density (individuals/m²), based on size-specific belt area.", "biomass_g", "FLOAT", FALSE, "Total biomass (g), calculated using length–weight relationship.", "biomass_gm2", "FLOAT", FALSE, "Biomass per square meter (g/m²), standardized by belt area.", # Notes "notes", "STRING", FALSE, "Optional comments or QA annotations.")gt(blt_obs_fields) |> cols_label(field = md("**Field**"), type = md("**Type**"), required = md("**Required**"), description = md("**Description**")) |> cols_width(field ~ px(200), type ~ px(100), required ~ px(80), description ~ px(500)) |> data_color(columns = c(field), fn = scales::col_factor(palette = c("#f6f6f6"), domain = NULL) ) |> tab_options(table.font.size = px(13), table.width = pct(100)) |> fmt_tf(columns = required, tf_style = "true-false") |> fmt_markdown(columns = description) |> gt_theme_nytimes()```::: {.callout-note title="Off-transect observations"}Fish observed outside of standard transects (e.g., large pelagics, megafauna) are recorded using `transect = "OFF"`. These are included for completeness but should be treated separately in standardized analyses.:::```{r}# Define table schemablt_obs_schema <- blt_obs_fields |>mutate(mode =if_else(required, "REQUIRED", "NULLABLE")) |>transmute(name = field,type = type,mode = mode,description = description) |>pmap(function(name, type, mode, description) {list(name = name, type = type, mode = mode, description = description) })bq_table_create(bq_table(project_id, "uvs", "blt_observations"),fields = blt_obs_schema)```##### Biomass by taxa and station (`uvs.blt_biomass_by_taxa`)This table contains station-level summaries of reef fish abundance and biomass by species, derived from raw BLT transect observations. It serves as the primary analysis-ready table for assessing community structure, trophic composition, and biomass patterns.Each row represents a unique combination of `ps_station_id` and species. Metrics are expressed as **mean values across all transects** at the station, with **zeros imputed for transects where the species was not observed**. This approach ensures unbiased station-level estimates of:- count_m2 - average density (individuals/m²)- biomass_gm2 - average biomass (g/m²)Species identity is standardized via `accepted_aphia_id`, with taxonomic and ecological traits (e.g., family, trophic group) joined from `taxonomy.fish`. These fields support flexible aggregation by trophic or functional guild.```{r eval = T, include = T}#| label: tbl-blt-biomass-schema#| tbl-cap: "Schema for station-level summaries of reef fish biomass and abundance by species"blt_biomass_by_taxa_fields <- tribble( ~field, ~type, ~required, ~description, # Identifiers "ps_station_id", "STRING", TRUE, "Unique station ID (`ps_site_id_depth`), e.g., `CHL_2024_uvs_001_20m`.", "accepted_name", "STRING", TRUE, "Scientific name of the taxon (`Genus species`).", "accepted_aphia_id", "INTEGER", TRUE, "Unique AphiaID from WoRMS; foreign key to `taxonomy.fish`.", # Taxonomic traits "family", "STRING", TRUE, "Family of the taxon (joined from `taxonomy.fish`).", "trophic_group", "STRING", TRUE, "Trophic group classification (e.g., `planktivore`, `top-predator`).", # Station spatial context "depth_m", "FLOAT", TRUE, "Mean depth (m) of the station.", "depth_strata", "STRING", TRUE, "Depth bin label: `supershallow`, `shallow`, or `deep`.", "region", "STRING", TRUE, "Region name.", "subregion", "STRING", TRUE, "Subregion (e.g., island, gulf, reef complex).", "locality", "STRING", FALSE, "Optional local feature (e.g., reef, bay, cove).", "habitat", "STRING", TRUE, "Dominant habitat type at the station.", "exposure", "STRING", TRUE, "Wave/wind exposure at the station (`windward`, `leeward`, `lagoon`).", # Aggregated metrics "total_count", "INTEGER", FALSE, "Total number of individuals observed across all transects.", "avg_count", "FLOAT", FALSE, "Mean number of individuals per transect (including zeros).", "avg_count_m2", "FLOAT", FALSE, "Mean density (individuals/m²), with zeros imputed for missing transects.", "avg_biomass_gm2", "FLOAT", FALSE, "Mean biomass (g/m²), with zeros imputed for missing transects.", "avg_length_cm", "FLOAT", FALSE, "Weighted mean total length (cm); weights based on individual counts.", "min_length_cm", "FLOAT", FALSE, "Minimum total length (cm) observed at the station.", "max_length_cm", "FLOAT", FALSE, "Maximum total length (cm) observed at the station.")gt(blt_biomass_by_taxa_fields) |> cols_label(field = md("**Field**"), type = md("**Type**"), required = md("**Required**"), description = md("**Description**")) |> cols_width(field ~ px(200), type ~ px(100), required ~ px(80), description ~ px(500)) |> data_color(columns = c(field), fn = scales::col_factor(palette = c("#f6f6f6"), domain = NULL) ) |> tab_options(table.font.size = px(13), table.width = pct(100)) |> fmt_tf(columns = required, tf_style = "true-false") |> fmt_markdown(columns = description) |> gt_theme_nytimes()``````{r}# Define table schemablt_biomass_schema <- blt_biomass_by_taxa_fields |>mutate(mode =if_else(required, "REQUIRED", "NULLABLE")) |>transmute(name = field,type = type,mode = mode,description = description) |>pmap(function(name, type, mode, description) {list(name = name, type = type, mode = mode, description = description) })bq_table_create(bq_table(project_id, "uvs", "blt_biomass_by_taxa"),fields = blt_biomass_schema)```